/*
 ============================================================================
 Name        : tuntap.c
 Author      : zhanglei
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <linux/if.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
#include <linux/if_tun.h>
#include <linux/if_ether.h>
#include "tuntap.h"

/* open tun/tap device . You can specify tunX or tapX
 * where X refers to device number you want, or you can
 * simply pass tun or tap, then system will choose one
 * for you and returned by dev */
int tun_open(char *dev) {
    struct ifreq ifr;
    int fd;
    char *device = "/dev/net/tun";
    if ((fd = open(device, O_RDWR)) < 0) {
        printf("Cannot open TUN/TAP dev %s\n", device);
        return -1;
    }

    memset(&ifr, 0, sizeof(ifr));

    /* no packet information */
    ifr.ifr_flags = IFF_NO_PI;

    if (!strncmp(dev, "tun", 3)) {
        ifr.ifr_flags |= IFF_TUN;
    } else if (!strncmp(dev, "tap", 3)) {
        ifr.ifr_flags |= IFF_TAP;
    } else {
        printf("I don't recognize device %s as a TUN or TAP device", dev);
        close(fd);
        return -1;
    }

    if (strlen(dev) > 3) /* unit number specified? */
        strncpy(ifr.ifr_name, dev, IFNAMSIZ);

    if (ioctl(fd, TUNSETIFF, (void *) &ifr) < 0) {
        printf("Cannot ioctl TUNSETIFF %s\n", dev);
        close(fd);
        return -1;
    }
    printf("TUN/TAP device %s opened\n", ifr.ifr_name);

    strncpy(dev, ifr.ifr_name, IFNAMSIZ);
    return fd;
}

int tun_activate(char *dev) {

    struct ifreq ifr;
    int tmpfd = -1;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);
    ifr.ifr_flags = IFF_UP;

    tmpfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (ioctl(tmpfd, SIOCSIFFLAGS, (void *) &ifr) < 0)
    {
        if (errno != EEXIST)
        {
        }
        perror("ioctl(SIOCSIFFLAGS) failed");
        return -1;
    }

    close(tmpfd);
    return 0;
}
/* set the ip address of the interface which is
 * specified by device string. */
int tun_set_ip(char *dev, uint32_t ip)
{

    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);
    int tmpfd = socket(AF_INET, SOCK_DGRAM, 0);
    ifr.ifr_addr.sa_family = AF_INET;
    ifr.ifr_dstaddr.sa_family = AF_INET;
    ifr.ifr_netmask.sa_family = AF_INET;

    ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr.s_addr = ip;
    if (ioctl(tmpfd, SIOCSIFADDR, (void *) &ifr) < 0)
    {
        if (errno != EEXIST)
        {
        }
        perror("ioctl(SIOCSIFADDR) failed");
        return -1;
    }

    close(tmpfd);

    return 0;

}

/* set the netmask of the interface which is
 * specified by device string. */
int tun_set_netmask(char *dev, uint32_t mask)
{

    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);
    int tmpfd = 0;
    ifr.ifr_addr.sa_family = AF_INET;
    ifr.ifr_dstaddr.sa_family = AF_INET;
    ifr.ifr_netmask.sa_family = AF_INET;

    if ((tmpfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket");
        return -1;
    }
    ((struct sockaddr_in *) &ifr.ifr_netmask)->sin_addr.s_addr = mask;
    if (ioctl(tmpfd, SIOCSIFNETMASK, (void *) &ifr) < 0)
    {
        printf("ioctl(SIOCSIFNETMASK) failed\n");
        return -1;
    }

    close(tmpfd);

    return 0;

}

/* analysis and print packet information
 * should be called in debug mode only. */
int decode_packet(const char *pack) {

    struct iphdr *ip = (struct iphdr *) pack;
    struct tcphdr *tcp = NULL;
    struct udphdr *udp = NULL;
    struct icmphdr *icmp = NULL;
    switch (ip->protocol) {
        case IPPROTO_TCP:
            tcp = (struct tcphdr *) (pack + (ip->ihl) * 4);
            printf("TCP from %s:%d", inet_ntoa(*(IA *) &ip->saddr), ntohs(
                    tcp->source));
            printf(",to %s:%d ", inet_ntoa(*(IA *) &ip->daddr),
                    ntohs(tcp->dest));
            printf("syn=%d;ack=%d;fin=%d;rst=%d\n", tcp->syn, tcp->ack,
                    tcp->fin, tcp->rst);
            break;
        case IPPROTO_UDP:
            udp = (struct udphdr *) (pack + (ip->ihl) * 4);
            printf("UDP from %s:%d", inet_ntoa(*(IA *) &ip->saddr), ntohs(
                    udp->source));
            printf(",to %s:%d\n", inet_ntoa(*(IA *) &ip->daddr), ntohs(
                    udp->dest));
            break;
        case IPPROTO_ICMP:
        	icmp = (struct icmphdr *) (pack + (ip->ihl) * 4);
            printf("from %s", inet_ntoa(*(IA *) &ip->saddr));
            printf(",to %s\n", inet_ntoa(*(IA *) &ip->daddr));
        	printf("icmp code = %d, type = %d, chcksum = 0x%04x, id = 0x%04x, seq = 0x%04x\n",
        			icmp->code, icmp->type, icmp->un.echo.id, icmp->un.echo.sequence);
        	break;
        default:
            printf("unknow protocol:%d  ", ip->protocol);
            printf("from %s", inet_ntoa(*(IA *) &ip->saddr));
            printf(",to %s\n", inet_ntoa(*(IA *) &ip->daddr));
    }

    return 0;
}

/* add all uint16 numbers which buffer pionts to */
uint32_t chk_add(uint16_t *buffer, int size) {
    uint32_t cksum = 0;
    while (size > 1) {
        cksum += *buffer++;
        size -= sizeof(uint16_t);
    }
    if (size) {
        cksum += *(uint8_t *) buffer;
    }

    return cksum;
}

/* calculates final checksum */
uint16_t chk_calc(uint32_t cksum) {

    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >> 16);
    return (uint16_t) (~cksum);

}

/* packet checksum function .
 * first of all, it calculates IP checksum
 * and then TCP/UDP checksum depending on
 * protocol type. */
int pack_chksum(char *buf, int size) {

    struct iphdr *ip = (struct iphdr *) buf;
    uint16_t hlen = ip->ihl << 2;
    Pseudo_Header pshdr;

    /* set ip checksum */
    ip->check = 0;
    ip->check = chk_calc(chk_add((uint16_t *) ip, hlen));

    int pksum = 0;
    uint16_t pksize = size - hlen;

    get_ipaddr(buf, &pshdr.src, 0);
    get_ipaddr(buf, &pshdr.dst, 1);
    pshdr.zero = 0;
    pshdr.proto = ip->protocol;
    pshdr.len = htons(pksize);

    pksum += chk_add((uint16_t *) &pshdr, sizeof(pshdr));

    /* set TCP/UDP checksum */
    if (ip->protocol == IPPROTO_UDP) {
        struct udphdr *udp = (struct udphdr *) (buf + hlen);
        udp->check = 0;
        pksum += chk_add((uint16_t *) udp, pksize);
        udp->check = chk_calc(pksum);

    } else if (ip->protocol == IPPROTO_TCP) {
        struct tcphdr *tcp = (struct tcphdr *) (buf + hlen);
        tcp->check = 0;
        pksum += chk_add((uint16_t *) tcp, pksize);
        tcp->check = chk_calc(pksum);

    } else {
        /* add more protocol support */
    }

    return 0;

}

/* get ip address from packet buffer,
 * where = 0, get source address,
 * where = 1, get destination address. */
int get_ipaddr(char *buf, uint32_t *addr, int where)
{

    if (addr == NULL)
        return -1;

    struct iphdr *ip = (struct iphdr *) buf;

    if (where == 0)
        *addr = ip->saddr;
    else if (where == 1)
        *addr = ip->daddr;
    else
        return -1;

    return 0;
}

/* set ip address from packet buffer,
 * where = 0, set source address,
 * where = 1, set destination address. */
int set_ipaddr(char *buf, int size, uint32_t addr, int where)
{

    struct iphdr *ip = (struct iphdr *) buf;

    if (where == 0)
        ip->saddr = addr;
    else if (where == 1)
        ip->daddr = addr;
    else
        return -1;

    pack_chksum(buf, size);

    return 0;
}
